﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml.Serialization;

namespace Reflection
{
    public class EmittingCode
    {
        public static void Emit()
        {
            AssemblyName an = new AssemblyName();
            an.Name = "Rectangural";
            AppDomain ad = AppDomain.CurrentDomain;

            //Create Assembly
            AssemblyBuilder ab = ad.DefineDynamicAssembly(an, AssemblyBuilderAccess.Save);

            //Define Module
            ModuleBuilder mb = ab.DefineDynamicModule(an.Name,"Hello.exe");

            //Define Type
            TypeBuilder tb = mb.DefineType("Foo.Rectangural", TypeAttributes.Public | TypeAttributes.Class);

            //Define field
            FieldBuilder field = tb.DefineField("height", typeof (int), FieldAttributes.Private);

            //Define property
            PropertyBuilder prop = tb.DefineProperty("Height", PropertyAttributes.None, typeof (int),new Type[0]);

            MethodBuilder getter = tb.DefineMethod("get_Height", MethodAttributes.Public | MethodAttributes.SpecialName,
                typeof (int), new Type[0]);
            ILGenerator getGen = getter.GetILGenerator();
            getGen.Emit(OpCodes.Ldarg_0);
            getGen.Emit(OpCodes.Ldfld,field);
            getGen.Emit(OpCodes.Ret);

            MethodBuilder setter = tb.DefineMethod("set_Height",
                MethodAttributes.Assembly | MethodAttributes.SpecialName,
                null, new Type[] {typeof(int)});
            ILGenerator setGen = setter.GetILGenerator();
            setGen.Emit(OpCodes.Ldarg_0);
            setGen.Emit(OpCodes.Ldarg_1);
            setGen.Emit(OpCodes.Stfld,field);
            setGen.Emit(OpCodes.Ret);

            prop.SetGetMethod(getter);
            prop.SetSetMethod(setter);


            //define field
            FieldBuilder fieldW = tb.DefineField("width", typeof(int), FieldAttributes.Private);

            //define property
            PropertyBuilder propW = tb.DefineProperty("Width", PropertyAttributes.None, typeof(int), new Type[0]);

            MethodBuilder getterW = tb.DefineMethod("get_Width", MethodAttributes.Public | MethodAttributes.SpecialName,
                typeof(int), new Type[0]);
            ILGenerator getGenW = getter.GetILGenerator();
            getGen.Emit(OpCodes.Ldarg_0);
            getGen.Emit(OpCodes.Ldfld, fieldW);
            getGen.Emit(OpCodes.Ret);

            MethodBuilder setterW = tb.DefineMethod("set_Width",
                MethodAttributes.Assembly | MethodAttributes.SpecialName,
                null, new Type[] { typeof(int) });
            ILGenerator setGenW = setter.GetILGenerator();
            setGen.Emit(OpCodes.Ldarg_0);
            setGen.Emit(OpCodes.Ldarg_1);
            setGen.Emit(OpCodes.Stfld, fieldW);
            setGen.Emit(OpCodes.Ret);

            prop.SetGetMethod(getterW);
            prop.SetSetMethod(setterW);

            //Define Constructor


            //Define Method
            MethodBuilder mber = tb.DefineMethod("RectanguralArea",
                MethodAttributes.Static | MethodAttributes.Public,
                CallingConventions.Standard,
                typeof (double),
                new[] {typeof (double), typeof (double)});

            mber.DefineParameter(1, ParameterAttributes.None, "height");
            mber.DefineParameter(2, ParameterAttributes.None, "Width");
            ILGenerator gen = mber.GetILGenerator();
            gen.Emit(OpCodes.Ldarg_0);
            gen.Emit(OpCodes.Ldarg_1);
            gen.Emit(OpCodes.Mul);
            gen.Emit(OpCodes.Ret);

            //Attaching Attributes  [XmlElement("First Name",Namespace="http://test/",Order=3)]
            Type attType = typeof (XmlElementAttribute);
            ConstructorInfo attConstructor = attType.GetConstructor(new Type[] {typeof (string)});
            var att = new CustomAttributeBuilder(
                attConstructor,    //Constructor
                new object[] {"FirstName"},   //Parameter
                new PropertyInfo[]
                {
                    attType.GetProperty("Namespace"),
                    attType.GetProperty("Order")
                },
                new object[] { "http://test/",3}
            );

            tb.SetCustomAttribute(att);
        }





        public static void CreateMyAsm(AppDomain curAppDomain)
        {
            //Establish general assembly characteristics
            AssemblyName assemblyName = new AssemblyName();
            assemblyName.Name = "MyAssembly";
            assemblyName.Version = new Version("1.0.0.0");

            // Create new assembly within The AppDomain
            AssemblyBuilder assembly = curAppDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save);

            //Given that we are building single-file
            //assembly, the name of Module is same as assembly name
            ModuleBuilder module = assembly.DefineDynamicModule("MyAssembly", "MyAssembly.dll");

            //Define Public class name hello
            TypeBuilder helloWorldClass = module.DefineType("MyAssembly.HelloWorld", TypeAttributes.Public);

            //Define private member string theMessage
            FieldBuilder msgField = helloWorldClass.DefineField("theMessage", Type.GetType("System.String")
                                                ,FieldAttributes.Private);

            //create the custom ctor
            Type[] constructorArgs = new Type[1];
            constructorArgs[0] = typeof (string);
            ConstructorBuilder constructor =
                helloWorldClass.DefineConstructor(MethodAttributes.Public,
                    CallingConventions.Standard,
                    constructorArgs);
            ILGenerator constructorIL = constructor.GetILGenerator();
            constructorIL.Emit(OpCodes.Ldarg_0);
            Type objectClass = typeof (object);
            ConstructorInfo superConstructor =
                objectClass.GetConstructor(new Type[0]);
            constructorIL.Emit(OpCodes.Call,superConstructor);
            constructorIL.Emit(OpCodes.Call,superConstructor);
            constructorIL.Emit(OpCodes.Ldarg_0);
            constructorIL.Emit(OpCodes.Ldarg_1);
            constructorIL.Emit(OpCodes.Stfld,msgField);
            constructorIL.Emit(OpCodes.Ret);

            //Create the default ctor
            helloWorldClass.DefineDefaultConstructor(MethodAttributes.Public);
            //Now create the GetMsg() method
            MethodBuilder getMsgMethod =
                helloWorldClass.DefineMethod("GetMsg", MethodAttributes.Public,
                    typeof (string), null);
            ILGenerator methodIL = getMsgMethod.GetILGenerator();
            methodIL.Emit(OpCodes.Ldarg_0);
            methodIL.Emit(OpCodes.Ldfld,msgField);
            methodIL.Emit(OpCodes.Ret);

            //Create the SayHello method
            MethodBuilder sayHiMethod =
                helloWorldClass.DefineMethod("SayHello",
                    MethodAttributes.Public, null, null);
            methodIL = sayHiMethod.GetILGenerator();
            methodIL.EmitWriteLine("Hello The World");
            methodIL.Emit(OpCodes.Ret);

            //"Bake" the class Helloworld
            helloWorldClass.CreateType();

            //(Optionally) save the assembly to file
            assembly.Save("MyAssembly.dll");
            
        }
    }
}
